

ozelokul.com
SaaSA private-school, college and university discovery platform with a parent centre and school panels.
INDUSTRY
A school SaaS, an enrolment site, an AI tutor and student apps: the permission model, KVKK consent records and testing from our education work.
SHORT ANSWER
AMG Software Studio has built a school discovery and management SaaS, an enrolment-focused school website, an AI tutor and student apps. The largest is ozelokul.com: 139 migrations, row-level security on every table, an entitlement engine with 3 plans and 56 permission keys, and 81 provinces with 973 districts. 80 server mutations were audited with live exploit attempts and the four holes found were closed.
Most private schools in Turkey are not a single building but a group with several campuses. At ozelokul.com we therefore made the brand (the school group) the tenant and the campus a foreign-keyed scope inside it. The link between a person and an organisation lives in a membership table, with campus scope in a table of its own.
Roles are permission keys, not an enum. The schema we inherited had a super_admin|parent_admin|school_admin enum and a column tying a user to a school by string; both went, because adding a role should not need a migration. Today there are 7 role templates (group admin, campus principal, admissions office, marketing, accounting, front desk, super admin) and 56 permission keys.
Every check goes through one function, private.has_perm(user, perm, brand, campus), and the order matters: a deleted account is always refused, then brand and campus consistency, an active membership, the permission list and finally campus scope are checked. Subscriptions face the same question, and the subscriptions table lets a plan be sold to either the brand or a single campus.
ozelokul.com is built around a parent choosing a school and the school running its operations. School, college and university profiles, open seats, a scholarship exam calendar, early registration and discounts, and a price index with a published methodology sit on the public side. The parent centre has alarms, surveys, matching, cost calculation and journey screens. Schools get a 22-section panel, an application → documents → approval → onboarding flow, moderation and a parent CRM.
The boundary of the scope is written down too. Teachers are not users in this product: the design has no teacher login, so a teacher record is a content row. There is no attendance or grading module. A column is already left nullable in case a teacher panel is requested later, but the schema was not padded with imaginary modules.
If what you need is in-school work such as timetables, attendance or grade entry, we define those as separate work packages during discovery. Drawing the scope honestly at the start avoids tables that are hard to remove later.
At ozelokul.com KVKK (Turkey’s data protection law) is a traceable chain in the database, not a cookie banner. Every form consent writes a row to consent_records: the user or anonymous session id, the id of the text agreed to, the context (quote, appointment, application, newsletter and so on), a timestamp and a revocation time. A check constraint forces every record to belong either to a user or to an anonymous session.
Legal texts are versioned. A verification round found that an admin could edit a published text in place, which would have made old consents silently point at different content. A published text can now be changed by nobody; only a new version can be published.
When an account is deleted the rows stay but personal fields are cleared: phone and neighbourhood on the parent profile, name and special-needs note on the child record, and the request record lists the 12 tables touched. Schools must see the parent quote pool without seeing contact details, so a view rather than a policy does the work and contact columns come back empty until the parent reveals them. This describes what the software does; it is not legal advice.
Search: Postgres ilike does not handle Turkish characters. Searching kadikoy returned 0 results and kadıköy returned 1, so a parent typing without diacritics found nothing and saw no error. A trigger-maintained normalised search column with a trigram index was added, and the search term is normalised by the same rules.
PDF: jsPDF’s built-in Helvetica lacks ı, İ, ğ, Ğ, ş and Ş. Roboto was subset to the Turkish ranges, from 515 KB to 39 KB per style, and embedded; the library only loads when someone presses the download button.
Location and taxonomy: 81 provinces and 973 districts are seeded as foreign keys, not free text, and new terms in the 411-term taxonomy can only be added from the platform panel. A tag that is not on the list blocks publishing with a Turkish error message.
Payments: by client decision there is no payment provider. The flow runs on bank transfer (havale/EFT): the school reports the payment from its panel, and admin approval updates the invoice, the subscription and the audit log in one transaction. The parent notification queue is channel-agnostic; if no SMS or WhatsApp provider is configured, the record simply waits in the queue.
The ozelokul.com client arrived with a finished design running entirely on mock data: about 53 routes and 50,000 lines. The brief was to build a production-grade backend underneath without touching a single pixel. Every field of the mock data was first mapped to the schema and then loaded as a seed migration; the whole engagement took 4 months.
The admin panel has three layers: hand-built workflow screens, acting on behalf of any school inside its own panel, and a data browser generated from the schema. The last one solved a real problem: the admin had the permission, but most of the 149 tables had no screen. When a column is added, the screen updates by itself.
Auditing happens at two levels. In the database, 560 assertions in 33 pgTAP files test anonymous reads, plan differences, campus scope and masking, and each suite checks which role it is actually running as. On the server, 80 mutation endpoints were mapped to their gates and 30 were tried live with real JWTs. Four findings were exploited, among them an invitation token not bound to the invitee’s email, and all were closed in both code and migrations.
On Sora Okulları’s old site information was scattered and the enrolment form sat at the very bottom of the page. Parents want to know a school before filling in a form, and the school had a method to explain (Sora Kaizen) and more than one campus.
We ordered the content by the questions parents actually ask: about, the Sora Kaizen method, campus environment, campuses, for parents, careers, investors, partners and blog are separate pages. The enrolment call repeats at the end of each section, so the form is always one tap away.
The technical side was kept simple. The site is statically generated on the Next.js App Router and runs on Vercel; content lives in a single data file, which is also where campuses and contact details are updated. Page titles and descriptions target each campus and district for local search. The project took 5 weeks and soraokullari.com is live.
The example makes one point: not every education project is a SaaS. For a school site that is expected to bring in enrolments, the work is solved by content order and a repeating call to action more than by a database.
Sorudaş is an AI tutor that explains the reasoning rather than the answer. In exam prep a student has no way to notice when a model’s solution is wrong, so the trust gate is a pure function in the domain layer and an unverified solution is never taught.
Every model output passes a Zod schema, prompts live in a versioned registry, and a separate math-check package checks solutions against a golden set. architecture-guard proves the rule holds with 12 deliberate violations. The monorepo carries student, teacher, institution, admin and mobile apps; the parent panel, quota gate and right to deletion have been verified on live Supabase.
UniApp is an iOS app for university students: the events calendar, communities and content discovery are filtered by the interests each student picks, so not everyone sees the same feed.
Lilly & Leo is a story app for children. A short quiz after each chapter reinforces what was read, alongside daily tasks and a collection to build. The interface uses large touch targets and limited navigation with no external links; child safety was put first in the design.
If you need them, they are defined as separate work packages during discovery. ozelokul.com deliberately has no attendance or grading module, because the product covers school profiles, parent requests and school operations and the design has no teacher login. A nullable column is already in place in case a teacher panel is requested, so teacher logins can be added starting from it.
The tenant is the school group and the campus is a scope inside it. A user gets a role template, optionally fine-tuned per tab, and the result is intersected with campus scope. At ozelokul.com membership and scope are written in one transaction; before that, the missing scope row between steps briefly widened a membership to every campus. Nobody can grant outside their own scope or grant a permission they do not hold.
In the database. At ozelokul.com a plan is not a condition in code but rows in plan_features: the gallery photo limit is 5 on Basic and 40 on Growth. One function decides whether a section shows on a public profile, and a locked section never comes back from the query. Hiding it in the front end is not enough, because the hidden data would still reach the browser.
Yes. At ozelokul.com the client chose not to use a payment provider, so the flow runs on bank transfer (havale/EFT). The platform issues an invoice, the school reports the payment from its panel, and on admin approval the invoice is marked paid, the subscription is activated and an audit record is written, all in one transaction. If card payments are needed, the provider is chosen during discovery and added to the scope.
In the first version of ozelokul.com they did not: kadikoy returned 0 results, kadıköy returned 1, and the user saw no error. A trigger-maintained normalised search column with a trigram index was added, and the search term is normalised by the same rules. A stored column was chosen because normalising inside the query would have stopped the index from being used.
In Sorudaş an unverified solution is never shown to the student. The trust gate is a pure function in the domain layer, every model output passes a Zod schema, and a separate math-check package checks the solution against a golden set. Bypassing the gate breaks the architecture rules, and that rule is tested with 12 deliberate violations. Because every external service has a mock adapter, the system also runs without any API key.
You do. The domain, servers, database and service accounts are opened in your name. At delivery we hand over the full source code, the design files and every credential.
No. We design the user screens and the admin panel screens for your project and build them from scratch, so the panel follows your workflow.
RELATED STUDIO NOTES


A private-school, college and university discovery platform with a parent centre and school panels.


A school corporate site and enrolment flow.


An AI tutor that explains the reasoning, not the answer.


A social and academic platform for university students.


An educational storytelling and learning app for children.
It starts with an intro call. We define the scope and timeline together, then send you a written proposal.
hello@amgsoftwarestudio.com